﻿// Copyright (c) 2012 Mokah Nnamezie & Andrew Cooper
// Neo4Net graph database - http://neo4net.codeplex.com
//
// This file is part of Neo4Net - a .Net port of the Neo4j graph database
//
// Neo4Net is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace Neo4Net.Kernel.Impl.Core
{
    using log4net;
    using log4net.Core;
    using log4net.Repository.Hierarchy;
    using Neo4Net.GraphDb;
    using Neo4Net.Tooling;
    using NUnit.Framework;
    using System;

    [TestFixture]
    public class TestNeo4NetConstrains : AbstractNeo4NetTestCase
    {
        private string key = "testproperty";

        [Test]
        public void TestDeleteReferenceNodeOrLastNodeIsOk()
        {
            ITransaction tx = Transaction;
            for (int i = 0; i < 10; i++)
            {
                GraphDb.CreateNode();
            }
            // long numNodesPre = NodeManager.GetNumberOfIdsInUse(typeof(INode));
            // empty the DB instance
            foreach (INode node in GlobalGraphOperations.At(GraphDb).GetAllNodes())
            {
                foreach (IRelationship rel in node.GetRelationships())
                {
                    rel.Delete();
                }
                node.Delete();
            }
            tx.Success();
            tx.Finish();
            tx = GraphDb.BeginTx();
            // the DB should be empty
            // long numNodesPost = NodeManager.GetNumberOfIdsInUse(typeof(INode));
            // Console.WriteLine(String.format( "pre: %d, post: %d", numNodesPre, numNodesPost));
            CollectionAssert.IsEmpty(GlobalGraphOperations.At(GraphDb).GetAllNodes());
            // TODO: this should be valid, fails right now!
            // Assert.That( 0, numNodesPost );
            Assert.That(() => GraphDb.ReferenceNode, Throws.TypeOf<NotFoundException>());
            tx.Success();
            tx.Finish();
        }

        [Test]
        public void TestDeleteNodeWithRel1()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            node1.Delete();
            Assert.That(() =>
            {
                ITransaction tx = Transaction;
                tx.Success();
                tx.Finish();
            }, Throws.Exception);
            Transaction = GraphDb.BeginTx();
        }

        [Test]
        public void TestDeleteNodeWithRel2()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            node2.Delete();
            node1.Delete();
            Assert.That(() =>
            {
                ITransaction tx = Transaction;
                tx.Success();
                tx.Finish();
            }, Throws.Exception);
            Transaction = GraphDb.BeginTx();
        }

        [Test]
        public void TestDeleteNodeWithRel3()
        {
            // make sure we can delete in wrong order
            INode node0 = GraphDb.CreateNode();
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            IRelationship rel0 = node0.CreateRelationshipTo(node1, MyRelTypes.Test);
            IRelationship rel1 = node0.CreateRelationshipTo(node2, MyRelTypes.Test);
            node1.Delete();
            rel0.Delete();
            ITransaction tx = Transaction;
            tx.Success();
            tx.Finish();
            Transaction = GraphDb.BeginTx();
            node2.Delete();
            rel1.Delete();
            node0.Delete();
        }

        [Test]
        public void TestCreateRelOnDeletedNode()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            ITransaction tx = Transaction;
            tx.Success();
            tx.Finish();
            tx = GraphDb.BeginTx();
            node1.Delete();
            ClearCache();
            // Create of rel on deleted node should fail test
            Assert.That(() => node1.CreateRelationshipTo(node2, MyRelTypes.Test), Throws.Exception);
            Assert.That(() =>
            {
                tx.Failure();
                tx.Finish();
            }, Throws.Exception);
            Transaction = GraphDb.BeginTx();
            node2.Delete();
            node1.Delete();
        }

        [Test]
        public void TestAddPropertyDeletedNode()
        {
            INode node = GraphDb.CreateNode();
            node.Delete();
            Assert.That(() => node.SetProperty(key, 1), Throws.Exception);
        }

        [Test]
        public void TestRemovePropertyDeletedNode()
        {
            INode node = GraphDb.CreateNode();
            node.SetProperty(key, 1);
            node.Delete();
            Assert.That(() =>
            {
                node.RemoveProperty(key);
                ITransaction tx = Transaction;
                tx.Success();
                tx.Finish();
            }, Throws.Exception);
        }

        [Test]
        public void TestChangePropertyDeletedNode()
        {
            INode node = GraphDb.CreateNode();
            node.SetProperty(key, 1);
            node.Delete();
            Assert.That(() =>
            {
                node.SetProperty(key, 2);
                ITransaction tx = Transaction;
                tx.Success();
                tx.Finish();
            }, Throws.Exception);
        }

        [Test]
        public void TestAddPropertyDeletedRelationship()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            IRelationship rel = node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            rel.Delete();
            Assert.That(() =>
            {
                rel.SetProperty(key, 1);
                ITransaction tx = Transaction;
                tx.Success();
                tx.Finish();
            }, Throws.Exception);
            node1.Delete();
            node2.Delete();
        }

        [Test]
        public void TestRemovePropertyDeletedRelationship()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            IRelationship rel = node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            rel.SetProperty(key, 1);
            rel.Delete();
            Assert.That(() =>
            {
                rel.RemoveProperty(key);
                ITransaction tx = Transaction;
                tx.Success();
                tx.Finish();
            }, Throws.Exception);
            node1.Delete();
            node2.Delete();
        }

        [Test]
        public void TestChangePropertyDeletedRelationship()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            IRelationship rel = node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            rel.SetProperty(key, 1);
            rel.Delete();
            Assert.That(() =>
            {
                rel.SetProperty(key, 2);
                ITransaction tx = Transaction;
                tx.Success();
                tx.Finish();
            }, Throws.Exception);
            node1.Delete();
            node2.Delete();
        }

        [Test]
        public void TestMultipleDeleteNode()
        {
            INode node1 = GraphDb.CreateNode();
            node1.Delete();
            Assert.That(() =>
            {
                node1.Delete();
                ITransaction tx = Transaction;
                tx.Success();
                tx.Finish();
            }, Throws.Exception);
        }

        [Test]
        public void TestMultipleDeleteRelationship()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            IRelationship rel = node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            rel.Delete();
            node1.Delete();
            node2.Delete();
            Assert.That(() =>
            {
                rel.Delete();
                ITransaction tx = Transaction;
                tx.Success();
                tx.Finish();
            }, Throws.Exception);
        }

        [Test]
        public void TestIllegalPropertyType()
        {
            var log = (Logger)LogManager.GetLogger(typeof(NodeManager)).Logger;
            var level = log.EffectiveLevel;
            log.Level = Level.Off;
            try
            {
                INode node1 = GraphDb.CreateNode();
                Assert.That(() => node1.SetProperty(key, new Object()), Throws.Exception);
                Assert.That(() =>
                {
                    ITransaction tx = Transaction;
                    tx.Success();
                    tx.Finish();
                }, Throws.Exception);
                Transaction = GraphDb.BeginTx();
                Assert.That(() => GraphDb.GetNodeById(node1.Id), Throws.Exception);
                node1 = GraphDb.CreateNode();
                INode node2 = GraphDb.CreateNode();
                IRelationship rel = node1.CreateRelationshipTo(node2, MyRelTypes.Test);
                Assert.That(() => rel.SetProperty(key, new Object()), Throws.Exception);
                Assert.That(() =>
                {
                    ITransaction tx = Transaction;
                    tx.Success();
                    tx.Finish();
                }, Throws.Exception);
                Transaction = GraphDb.BeginTx();
                Assert.That(() => GraphDb.GetNodeById(node1.Id), Throws.TypeOf<NotFoundException>());
                Assert.That(() => GraphDb.GetNodeById(node2.Id), Throws.TypeOf<NotFoundException>());
            }
            finally
            {
                log.Level = level;
            }
        }

        [Test]
        public void TestNodeRelDeleteSemantics()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            IRelationship rel1 = node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            IRelationship rel2 = node1.CreateRelationshipTo(node2, MyRelTypes.Test);
            node1.SetProperty("key1", "value1");
            rel1.SetProperty("key1", "value1");

            NewTransaction();
            node1.Delete();
            Assert.That(() => node1.GetProperty("key1"), Throws.TypeOf<InvalidOperationException>());
            Assert.That(() => node1.SetProperty("key1", "value2"), Throws.TypeOf<InvalidOperationException>());
            Assert.That(() => node1.RemoveProperty("key1"), Throws.TypeOf<InvalidOperationException>());
            node2.Delete();
            Assert.That(() => node2.Delete(), Throws.TypeOf<InvalidOperationException>());
            ClearCache();
            Assert.That(GraphDb.GetNodeById(node1.Id), Is.EqualTo(node1));
            Assert.That(GraphDb.GetNodeById(node2.Id), Is.EqualTo(node2));
            Assert.That(() => node1.GetProperty("key1"), Throws.TypeOf<InvalidOperationException>());
            Assert.That(() => node1.SetProperty("key1", "value2"), Throws.TypeOf<InvalidOperationException>());
            Assert.That(() => node1.RemoveProperty("key1"), Throws.TypeOf<InvalidOperationException>());
            Assert.That(rel1.GetProperty("key1"), Is.EqualTo("value1"));
            rel1.Delete();
            Assert.That(() => rel1.Delete(), Throws.TypeOf<InvalidOperationException>());
            Assert.That(() => rel1.GetProperty("key1"), Throws.TypeOf<InvalidOperationException>());
            Assert.That(() => rel1.SetProperty("key1", "value2"), Throws.TypeOf<InvalidOperationException>());
            Assert.That(() => rel1.RemoveProperty("key1"), Throws.TypeOf<InvalidOperationException>());
            ClearCache();
            Assert.That(GraphDb.GetRelationshipById(rel1.Id), Is.EqualTo(rel1));
            Assert.That(() => rel1.GetProperty("key1"), Throws.TypeOf<InvalidOperationException>());
            Assert.That(() => rel1.SetProperty("key1", "value2"), Throws.TypeOf<InvalidOperationException>());
            Assert.That(() => rel1.RemoveProperty("key1"), Throws.TypeOf<InvalidOperationException>());
            Assert.That(() => node2.CreateRelationshipTo(node1, MyRelTypes.Test), Throws.TypeOf<InvalidOperationException>());
            ClearCache();
            Assert.That(() => node2.CreateRelationshipTo(node1, MyRelTypes.Test), Throws.TypeOf<InvalidOperationException>());

            Assert.That(node1.GetSingleRelationship(MyRelTypes.Test, Direction.Outgoing), Is.EqualTo(rel2));
            ClearCache();
            Assert.That(node2.GetSingleRelationship(MyRelTypes.Test, Direction.Incoming), Is.EqualTo(rel2));

            ClearCache();
            Assert.That(rel1.GetStartNode(), Is.EqualTo(node1));
            ClearCache();
            Assert.That(rel2.GetEndNode(), Is.EqualTo(node2));
            INode[] nodes = rel1.GetNodes();
            Assert.That(nodes[0], Is.EqualTo(node1));
            Assert.That(nodes[1], Is.EqualTo(node2));
            ClearCache();
            Assert.That(rel1.GetOtherNode(node1), Is.EqualTo(node2));
            rel2.Delete();
            // will be marked for rollback so commit will throw exception
            Rollback();
        }
    }
}
